package org.example;

import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Random;
import java.io.IOException;

import java.io.*;
import java.net.*;
import java.nio.file.*;


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;


public class Simulacao {


    public static final int PORT = 9876; // Choose any available port

    public static ServerSocket serverSocket;// = new ServerSocket(PORT);
 
    public static Socket clientSocket;

    public static PrintWriter Respostador;


    public static void startBridge () throws IOException {

        serverSocket = new ServerSocket(PORT);

    }



    public static double[] pegaParametrosRede () throws IOException {


                clientSocket = serverSocket.accept();
                
                try {
                     BufferedReader in = new BufferedReader
                     (new InputStreamReader(clientSocket.getInputStream()));

                     Respostador = new PrintWriter(clientSocket.getOutputStream(), true);
                    
                    // Read parameter file path from bridge
                    String paramFilePath = in.readLine();
                    
                    if (paramFilePath == null || paramFilePath.equals("SHUTDOWN")) {
                        System.err.println("Shutdown command received");
                        double [] termina_esse_troco_ai = {-1, -1, -1, -1, -1, -1, -1, -1};
                        return termina_esse_troco_ai;
                    }
                    
                    // Read parameters from file
                    double[] parameters = readParametersFromFile(paramFilePath);
                    
                    return parameters;
                    
                } catch (Exception e) {
                    System.err.println("Error handling request: " + e.getMessage());
                }

                        double [] termina_esse_troco_ai = {-1, -1, -1, -1, -1, -1, -1, -1};
                        return termina_esse_troco_ai;
       };

    public static double[] readParametersFromFile(String filePath) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filePath)));
        String[] tokens = content.trim().split("\\s+");
        
        double[] parameters = new double[tokens.length];
        for (int i = 0; i < tokens.length; i++) {
            parameters[i] = Double.parseDouble(tokens[i]);
        }
        
        return parameters;
    }


    public static void respondeRede (double resultado) throws IOException {

        Respostador.println("" + resultado);

    }




    public static double[][] GCombinacoesProbabilidades = {{0,0,0,0,0,0,0,0}};

    static int[] DadosReais = {1,4,4,1,3,7,9,12,9,12,3,4,8,8,6,6,9,7,7,14,16,22,21,17,24,5,16,36,41,
									20,9,6,3,10,10,11,17,23,7,6,19,5,25,20,21,7,4,17,29,21,28,8,12,5,
									19,20,19,21,22,18,9,19,25,32,18,13,18,9,24,23,20,34,40,16,3,34,25,
									25,38,47,10,8,54,67,49,59,61,45,20,65,55,51,71,179,35,24,101,479,83,
									116,190,36,15,441,266,378,201,208,39,68,635,373,444,442,374,47,45,
									1130,412,536,528,710,33,38,930,363,752,724,631,35,32,1094,325,734,
									728,532,61,457,654,433,358,393,1452,31,44,1118,472,532,546,352,43,
									29,967,472,642,408,534,19,17,859,217,510,382,327,581,13,227,181,597,
									510,508,26,28,961,232,620,827,176,27,690,134,306,270,534,471,555,197,
									454,542,235,499,299,396,148,415,190,393,494,325,465,8,280,254,309,413,
									304,142,19,426,282,373,327,339,367,10,166,311,168,421,326,372,8,250,274,
									205,429,315,442,13,133,352,402,332,419,409,24,550,291,296,484,397,837,19,
									515,410,1070,704,797,41,26,2161,892,1507,1470,680,523,989,1568,836,1518,
									1509,1629,1573,726,1523,1126,1012,1691,1908,1635,1215,728,1101,1453,1627,
									1508,1308,605,822,1032,1361,1025,945,633,651,1355,423,665,43,28,1956,25,
									678,838,840,30,23,1638,34,977,998,401,889,925,622,47,1169,1040,1158,668,
									829,543,43,781,728,879,669,462,520,24,909,63,435,452,792,23,18,984,450,
									304,488,448,315,20,515,444,512,407,283,162,19,681,142,312,919,636,53,38,
									1682,643,867,1026,358,987,51,1623,347,1311,1059,733,685};


    static FileWriter  GCurvasCSV, GwriterTree;

    static boolean GFlagCurvas = false;
    static boolean GFlagPanic  = false;

    static class Pessoa {
        int estado; // 0/1/2  S/E/I/R
        int periodoInfeccao;
        int periodoLatencia;
        int sexo;
        int idade;
        int atingidos;
        int datainfec;
        int serialinterval;
    }

    static final int QUANTAS_PESSOAS    = 1774000;
    static final int QUANTOS_DIAS       = 8*30;         // 240 DIAS
    static final int QUANTOS_VIZINHOS   = 30;
    public static final double GRAPH_IRREGULAR  = 0.05;

    static final int INICIAL_INFECTADOS = 5;

    static final int PERIODO_INFECCAO     = 10;
    static final int PERIODO_LATENCIA     = 3;

    static int NPROBPROPAGACAO      = 8;  // Deve ser divisor inteiro de QUANTOS_DIAS


    static int [] OsResultados = new int[360];


    // Gênero dos agentes.
    static final int FEMININO   = 0;
    static final int MASCULINO  = 1;


    // Estados dos agentes.
    static final int SUSCETIVEL = 0;
    static final int INFECTADO  = 1;
    static final int PROPAGANDO = 2;
    static final int RECUPERADO = 3;


    //static double probabilidadeEncontro = 0.6;

    // Gerador aleatório global, utilizado para
    //   criar grafo e população.
    static Random Grandom                = new Random(123);

    static Pessoa Gpopulacao[]          = new Pessoa[QUANTAS_PESSOAS];


    static ArrayList<ArrayList<Integer>> Graph = new ArrayList<>();

    // Contem pessoas infectadas no dia de hoje.
    static int    GNinfectados = 0;
    static int [] Ginfectados  = new int[QUANTAS_PESSOAS+2];


    // Contem erro da simulação.
    static double ErroTotal;

    static int MonteCarloAcumuladoISim;



    //----------------------------------------
    // Controle de propagação da doença.

    // Quantos parâmetros serão utilizados?
    // Para uma simulação de 8 meses, o valor 4 significa que
    //    a taxa de probabilidade é ajustada a cada dois meses.
    static double [] GProbPropagacao = new double[12];
    static int       GIdxPropagacao;





    /**
     * Reads a CSV file, extracts columns 2 to 5, and stores them in the
     * global 'data' array.
     *
     * @param filePath The path to the CSV file to be read.
     */
    public static void LeiaTabelaDeParametros (String filePath) {

        // A list to temporarily hold the rows of data as we read them.
        List<double[]> rowList = new ArrayList<>();

        // Use a try-with-resources statement to ensure the BufferedReader is
        // closed automatically, even if an exception occurs.
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {

            String line;

            // Read the file line by line until the end is reached.
            int counter = 51;

            while ((counter > 0) && ((line = br.readLine()) != null)) {
                counter = counter - 1;
                // Split the line by the comma delimiter.
                String[] values = line.split(",");

                // Check if the line has enough columns.
                if (values.length == 4) {
                    try {

                        double[] row = new double[4];
                        
                        for (int k = 0; k < NPROBPROPAGACAO; k++)
                            row[k] = Double.parseDouble(values[k].trim());

                        // Add the new row to our temporary list.
                        rowList.add(row);

                    } catch (NumberFormatException e) {
                        System.err.println("Skipping line due to invalid number format: " + line);
                    }
                } else {
                    System.err.println("Skipping line due to insufficient columns: " + line);
                }
            }
        } catch (IOException e) {
            // Handle any exceptions that occur during file reading.
            System.err.println("Sem arquivo de parâmetros " + e.getMessage());
            System.exit (-1);
        }

        // Convert the list of arrays into a bidimensional array and
        // assign it to our global static variable.
        GCombinacoesProbabilidades = rowList.toArray(new double[0][]);
    }
    

   //-------------------------------------------------------------------------------


    
	static void criarPopulacao() {

        int[] faixasIdade = {100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 
                              30, 25, 20, 15, 10, 5, 0};

        int[] feminino    = {177, 1257, 4338, 9190, 15293, 23799, 34782, 45996, 55502, 61758, 
                             64882, 67863, 77758, 73885, 70520, 69948, 65713, 52763, 50181, 
                             49317, 41778};

        int[] masculino   = {60,  353, 1657, 4428, 9076, 15561, 24257, 33503, 42448, 49900, 
                             56005, 59424, 69645, 68339, 67694, 69001, 65900, 53638, 51777, 
                             51127, 43225};

        int totalPopulacaoDados = 0;

        for (int i = 0; i < feminino.length; i++)
            totalPopulacaoDados += feminino[i] + masculino[i];


        //Gpopulacao.clear();


        // Vamos gerar um grafo com N pessoas.
        for (int i = 0; i < QUANTAS_PESSOAS; i++) {

            Pessoa pessoa = new Pessoa();

            pessoa.estado        	= SUSCETIVEL;
            pessoa.periodoInfeccao 	= 0;
			   pessoa.periodoLatencia  = 0;
            pessoa.atingidos        = 0;
            pessoa.datainfec        = 0;
            pessoa.serialinterval   = 0;



            // Gere um número aleatório entre 0 e a soma das estatísticas
            //   (vetores "feminino[]" e "masculino[]".
            double num = Grandom.nextInt (totalPopulacaoDados);

            // Agora pesquisa nos vetores, em que ponto aquele número
            //   aleatório caiu.
            // Baseado nisso, inclua o indivíduo no grafo da simulação.
            int idx = 0;
            while ((num > 0) && (idx < feminino.length)) {

               num  = num - feminino[idx];

               // Caiu nesta faixa da população:
               // é mulher, idade randômica dentro da faixa (ex., "de 0 até 5")
               // Anote dados e saia do laço.
               if (num <= 0) {
                  pessoa.sexo  = FEMININO;
                  pessoa.idade = faixasIdade[idx] + Grandom.nextInt (5);
                  break;
               }

               num  = num - masculino[idx];

               // Caiu nesta faixa da população:
               // é homem, idade randômica dentro da faixa (ex., "de 0 até 5")
               // Anote dados e saia do laço.
               if (num <= 0) {
                  pessoa.sexo  = MASCULINO; 
                  pessoa.idade = faixasIdade[idx] + Grandom.nextInt (5);
                  break;
               }

               ++idx;
            }

            Gpopulacao[i] = pessoa;

        }

    }
	

    //---------------------------------------------------------------------

    // 
    // 
    // 
    // Vetores auxiliares devem ficar vazios.
    public static void resetEstadoSimulacao () {
      
        for (int i = 0; i < QUANTAS_PESSOAS; i++) {

            Gpopulacao[i].estado         = SUSCETIVEL;
            Gpopulacao[i].periodoInfeccao  = 0;
			   Gpopulacao[i].periodoLatencia  = 0;
        }

        GNinfectados = 0;
    } 


    //---------------------------------------------------------------------


    public static void wattsStrogatz(int population, 
                                     int numNeighbors, 
                                     int range, 
                                     double graphIrregular) {

        double desvioPadrao = range / 3.0;

        Graph.clear();
        Graph.ensureCapacity (population);

        // O grafo é um grande vetor; e 
        //    cada elemento do vetor é uma lista (vetor) de vizinhos.
        // Então temos um vetor de vetores.
        for (int i = 0; i < population; i++)

            Graph.add (new ArrayList<>());

        // Varra o vetor de pessoas, criando arestas = vizinhos.
        for (int nodeA = 0; nodeA < population; nodeA++) {

            // Vetor de vizinhos começa vazio.
            ArrayList<Integer> neighborsOfA = new ArrayList<>();

            // Quantos vizinhos serão gerados para nodeA?
            // Como é basicamente um ciclo, temos vizinhos à direita e à esquerda
            //  e portanto precisamos apenas da metade (variável radius).
            int q      = (int) (Grandom.nextGaussian() * desvioPadrao + numNeighbors);

            int radius = q / 2;

            if (radius < 1) radius = 1;

            // Quando for gerar uma aresta irregular, 
            //    evite escolher aquelas próximas (usadas no caso regular)
            int n4irregular = population - 1 - q;

            // Para cada vizinho, gere arestas à direita.
            // Quando circularmos a população inteira, haverão
            //    arestas vindas da esquerda totalizando o número de vizinhos.
            for (int j = 0; j <= radius; j++) {

                // Este é o vizinho.
                int nodeB = (nodeA + j + population) % population;

                // Isto nunca deveria acontecer, mas o teste está aqui.
                if (nodeA == nodeB) continue;

                // Se cair no teste para gerar grafo irregular,
                //   sorteie uma pessoa qualquer da população.
                if (Grandom.nextDouble () < graphIrregular) {

                    // Tente 5 vezes; em populações grandes, não haverá problema,
                    //    mas em pequenas há risco de coincidência.
                    for (int k = 0; k < 5; k++) {

                        // Escolha alguém fora dos vizinhos regulares.
                        int t        = (int) (n4irregular * Grandom.nextDouble ());
                        int nodeBirr = (nodeA + 1 + radius + t) % population;

                        // Verifique se já não foi selecionado para vizinho,
                        //   então aceite e saia do laço de 5 tentativas.
                        if (!neighborsOfA.contains (nodeBirr)) {
                            nodeB = nodeBirr;
                            break;
                        }
                    }
                }

                // Garanta que já não é vizinho; este teste
                //  é importante para grafo regular, é redundante se
                //  nodeB veio da geração de vizinho irregular.
                if (!neighborsOfA.contains (nodeB)) 
                   neighborsOfA.add (nodeB);

                // Vizinhança é biunívoca: é preciso "avisar"
                //   A e B que são vizinhos um do outro. 
                // Isso permite contaminação nos dois sentidos.
                if (!Graph.get (nodeB).contains (nodeA)) 
                   Graph.get (nodeB).add (nodeA);
            }

            // Registre para o nó A, os vizinhos recém-calculados.
            //Graph.set(nodeA, neighborsOfA);

            // Adicione ao nó A, os vizinhos recém-calculados.
            (Graph.get (nodeA)).addAll (neighborsOfA);
        }

    }

	

    //---------------------------------------------------------------------


    static double calcTaxaContagio(int idade, int sexo) {
        if (idade <  5) return (sexo == FEMININO) ? 0.1509 : 0.1627;
			else if (idade < 10) return (sexo == FEMININO) ? 0.1380 : 0.1413;
			else if (idade < 20) return (sexo == FEMININO) ? 0.2722 : 0.2284;
			else if (idade < 40) return (sexo == FEMININO) ? 0.5286 : 0.3967;
			else if (idade < 60) return (sexo == FEMININO) ? 0.4192 : 0.2980;
			else                 return (sexo == FEMININO) ? 0.1961 : 0.1775;
    }



	// Função para exportar a rede de populacao criada
	static void exportarGrafoCSV(String nomeArquivo) {
		try {
			FileWriter writer = new FileWriter(nomeArquivo);
			//writer.write("no1,no2\n"); // cabeçalho
			for (int i = 0; i < Graph.size(); i++) {
				for (int vizinho : Graph.get(i)) {
					writer.write(i + "," + vizinho + "\n");
				}
			}
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}



	static void exportarSubGrafoCSV(String nomeArquivo, int numeroNos) {
    try {
        FileWriter writer = new FileWriter(nomeArquivo);
        writer.write("no1,no2\n"); // cabeçalho
        
        int nosExportados = 0;
        
        for (int i = 0; i < Graph.size() && nosExportados < numeroNos; i++) {
            for (int vizinho : Graph.get(i)) {
                writer.write(i + "," + vizinho + "\n");
            }
            nosExportados++;
        }
        writer.close();
        System.out.println("Subgrafo exportado com sucesso!");
    } catch (IOException e) {
        e.printStackTrace();
    }
}



	//-------------------------------------------------------------------------------------------------
	
    static void executarSimulacao () {

        int AcumuladoIDados, AcumuladoISim;

        double erro = 0.0;


        // um número primo.
        //Grandom.setSeed (484459); 

        // Número inicial de infectados.
        int semente = INICIAL_INFECTADOS;

        AcumuladoISim   = semente;
        AcumuladoIDados = 0;

        // Infectar "semente" pessoas aleatoriamente.
        int j = 0;
        while (j < semente) {

            int idx = Grandom.nextInt (QUANTAS_PESSOAS);

            // Evite repetições
            if (SUSCETIVEL != Gpopulacao[idx].estado) continue;

            // Inicialize dados
            Gpopulacao[idx].estado           = INFECTADO;
            Gpopulacao[idx].periodoInfeccao  = PERIODO_INFECCAO;
			   Gpopulacao[idx].periodoLatencia  = PERIODO_LATENCIA;
			   Gpopulacao[idx].atingidos        = 0;
			   Gpopulacao[idx].datainfec        = 0;


            // Inclua no grupo de infectados
            Ginfectados[GNinfectados] = idx;
            ++GNinfectados;

            ++j;

        }


        // Laço principal dia-a-dia.
        LoopQuantosDias:
        for (int dia = 0; dia < QUANTOS_DIAS; dia++) {

            // Atualize contagem de pessoas infectadas
            //   segundo os dados reais.
            AcumuladoIDados += DadosReais[dia];

            // Array auxiliar; todos novos infectados são armazenados aqui.
            // Durante o cálculo de propagação evita-se re-infecção usando
            //    este array.
            // Ao final do cálculo, todos elementos são processados para
            //    atualizar estado do grafo, da população, etc.
            ArrayList<Integer> VInfectadosHoje     = new ArrayList<>();


//        for (int k = 0; k < NPROBPROPAGACAO; ++k) 
//           System.out.print ("" + GProbPropagacao[k] + ",");
//           System.out.print ("\n");

            // Índice no vetor GProbPropagacao.
            double probEncontro = 
                    GProbPropagacao[dia / (QUANTOS_DIAS / NPROBPROPAGACAO)]; 

            // Para cada pessoa do grupo de infectados,
            //    calcule novas pessoas infectadas e guarde em VI.
            for (int ii = 0; ii < GNinfectados; ii++) {

                // Pessoa que pode propagar a doença.
                int idxpropagador = Ginfectados[ii];

                // Ainda em incubação: atualize contador e
                //   salte para processar próximo indivíduo do grupo infectado.
                if (0 < Gpopulacao[idxpropagador].periodoLatencia) {

                   --Gpopulacao[idxpropagador].periodoLatencia;

                   continue;

                }

                // Se chegou aqui, indivíduo agora propaga a doença.
                Gpopulacao[idxpropagador].estado = PROPAGANDO;

                // Um dia a menos doente.
                --Gpopulacao[idxpropagador].periodoInfeccao;
              
                // Terminou prazo de infecção
                if (0 >= Gpopulacao[idxpropagador].periodoInfeccao) {

                   // Retire indivíduo do grupo
                   Ginfectados[ii]             = Ginfectados[GNinfectados-1];
                   Ginfectados[GNinfectados-1] = -1;
                   --GNinfectados;

                   // assinale que se recuperou
                   Gpopulacao[idxpropagador].estado = RECUPERADO;

                   // salte simulador para próximo indivíduo infectado.
                   continue;
                }


                ArrayList<Integer> osvizinhos;

                osvizinhos = Graph.get (idxpropagador);


                Random alguemRandom = new Random();

                //==========================
                // código para diminuir um vizinho
                //==========================
//                if ((dia > 30) && (osvizinhos.size() > 0)) 
//                   osvizinhos.remove (alguemRandom.nextInt (osvizinhos.size()));
                //==========================



                //==========================
                // código para acrescentar um vizinho
                //==========================

//                int SomaUm = alguemRandom.nextInt (QUANTAS_PESSOAS);
//
//                while (osvizinhos.contains (SomaUm))
//                   SomaUm = alguemRandom.nextInt (QUANTAS_PESSOAS);
//                
//                if ((dia > 30) && (dia < 37))
//                osvizinhos.add (SomaUm);


                //==========================




                // Processe cada vizinho de 'idxpropagador'.
                for (int idxVizinho : osvizinhos) {

                    //==========================
                    // código para diminuir número de vizinhos
                    //if ((dia > 0) && (idxVizinho == pulaUm)) { continue;}
                    //==========================

                    double probabilidadeespecial = 0;

//                    if (   (Gpopulacao[idxpropagador].idade >= 10)
//                        && (Gpopulacao[idxpropagador].idade <= 17)
//                        && (Gpopulacao[idxVizinho]   .idade >= 10)
//                        && (Gpopulacao[idxVizinho]   .idade <= 17)
//                        && (dia >= 21)
//                        && (dia <= 51))
//                       probabilidadeespecial = 0.5;

                    // Probabilidade resultou em não visitar.
                    if (Grandom.nextDouble () > probEncontro + probabilidadeespecial) continue;

                    // Chegou aqui visitou alguém, mas não era suscetível.
                    if (Gpopulacao[idxVizinho].estado != SUSCETIVEL) continue;

                    // Calcule dados da doença.
                    Pessoa vizinho = Gpopulacao[idxVizinho];

                    // Probabilidade de contrair.
                    if (Grandom.nextDouble () > 
                        calcTaxaContagio (vizinho.idade, vizinho.sexo)) continue;

                    // Se chegou aqui, 'idxpropagador' propagou.

                    // Anote árvore.
//                     try {
//                         GwriterTree.write("" + idxpropagador + "," + idxVizinho + "\n");
//                     } catch (Exception e) {
//                         e.printStackTrace();
//                     }

                    // Anote pessoa que será infectada.
                    VInfectadosHoje.add (idxVizinho);

                    // Altere estado (atenção; ainda falta processar variável VI).
                    Gpopulacao[idxVizinho].estado    = INFECTADO;
                    Gpopulacao[idxVizinho].datainfec = dia;
                    Gpopulacao[idxVizinho].serialinterval = dia - Gpopulacao[idxpropagador].datainfec;

                    
                    // Altere número de atingidos.
                    ++Gpopulacao[idxpropagador].atingidos;

                } // Trate próxima pessoa do grupo de infectados.

            } // varre grupo de já infectados e calcula propagação

            int aux = VInfectadosHoje.size ();

            // Termine simulação por falta de contágio ou exagero dos números.
            if ((aux > 10000)) {
               System.out.println ("GFlagPanic " + dia + " " + aux + "\n");
               GFlagPanic = true;
               return;
            }

            // Processe todos indivíduos do array "VI",
            //    atualizando estados e contadores.
            for (int novoDoente : VInfectadosHoje) {

               Gpopulacao[novoDoente].estado          = INFECTADO;
               Gpopulacao[novoDoente].periodoInfeccao = PERIODO_INFECCAO;
	    			Gpopulacao[novoDoente].periodoLatencia = PERIODO_LATENCIA;

               // Atualize grupo de pessoas infectadas.
        			Ginfectados[GNinfectados] = novoDoente;
               ++GNinfectados;

            } // processa novos infectados do dia.

            AcumuladoISim += VInfectadosHoje.size ();

            if (AcumuladoISim == QUANTAS_PESSOAS) {
               System.out.println ("População inteira atingida, dia = " + dia + "\n");
               ++AcumuladoISim;
            }

            if (GFlagCurvas)
            try{GCurvasCSV.write ("" + AcumuladoISim + ",");} catch (Exception e) {e.printStackTrace(); } 

            OsResultados[dia] += AcumuladoISim;

            // Acumule o erro de simulação do dia de hoje.
            erro += Math.pow (AcumuladoISim - AcumuladoIDados, 2); 

           //System.out.println (" " + dia);

        } // laço diário de simulação

        if (GFlagCurvas)
        try{GCurvasCSV.write ("\n");} catch (Exception e) {e.printStackTrace(); } 

        ErroTotal = Math.sqrt (erro / (double) QUANTOS_DIAS);

        MonteCarloAcumuladoISim += AcumuladoISim;

	}


   //-------------------------------------------------------------------

   public static double MonteCarlo (int nLoopsMonteCarlo) {

        for (int k = 0; k < NPROBPROPAGACAO; ++k) 
           System.out.print ("" + GProbPropagacao[k] + ",");

        System.out.print ("\n");

            for (int k = 0; k <= QUANTOS_DIAS; k++)
                OsResultados[k] = 0;

            int countMonteCarlo = 0;

            MonteCarloAcumuladoISim = 0;

            while (countMonteCarlo < nLoopsMonteCarlo) {

                countMonteCarlo = countMonteCarlo + 1;

                // todos indivíduos no estado 'suscetível'
                //    e todos contadores zerados.
                resetEstadoSimulacao();

                GFlagPanic = false;

                executarSimulacao();

                if (GFlagPanic) break;

            }

            MonteCarloAcumuladoISim = (int) (MonteCarloAcumuladoISim / nLoopsMonteCarlo);

            // Agora calcule o erro.
            int q = 0;
            double aux, erro = 0;

            if (!GFlagPanic) {

               for (int dia = 0; dia < QUANTOS_DIAS; dia++) {

                   q = q + DadosReais[dia];

                   OsResultados[dia] = (int)(OsResultados[dia] / nLoopsMonteCarlo);

                   aux = OsResultados[dia] - q;

                   erro += (aux * aux);
               }

               erro = Math.sqrt (erro / QUANTOS_DIAS);

            } else 
               erro = 1e6;

            return erro;

   }


    public static void performSensitivityAnalysis () {

//       double[] perturbations = {-0.03, -0.02, -0.01, 0.0, 0.01, 0.02, 0.03};
       double[] perturbations = {-0.3, -0.2, -0.1, 0.0, 0.1, 0.2, 0.3};
       double[] baselinePC    = {0.057, 0.039, 0.078, 0.042, 0.066, 0.022, 0.020, 0.020};


        for (int k = 0; k < NPROBPROPAGACAO; ++k)
           baselinePC[k] = GProbPropagacao[k];


        try (PrintWriter writer = new PrintWriter(new FileWriter("sensitivity.csv"))) {
            writer.println("period,perturbation_pct,pc_value,cases");
            
            // Baseline run
            System.out.println("Running baseline simulation...");
//            double[] baselineResult = sim(baselinePC); // Assuming sim returns [rmse, cases]

            MonteCarlo (100);

            int baselineCases = (int) MonteCarloAcumuladoISim;

            writer.printf("baseline,0.0,NA,%d%n", baselineCases);
            
            // For each time period
            for (int period = 0; period < baselinePC.length; period++) {
                System.out.printf("Analyzing period %d (PC = %.3f)%n", period, baselinePC[period]);
                
                // For each perturbation level
                for (double pertPct : perturbations) {
                    if (pertPct == 0.0) continue;
                    
                    double[] perturbedPC = baselinePC.clone();
                    perturbedPC[period] = baselinePC[period] * (1.0 + pertPct);
                    
                    // Ensure valid range
                    if (perturbedPC[period] < 0.0) perturbedPC[period] = 0.0;
                    if (perturbedPC[period] > 1.0) perturbedPC[period] = 1.0;
                    
                    for (int k = 0; k < NPROBPROPAGACAO; ++k)
                       GProbPropagacao[k] = perturbedPC[k];

                    MonteCarlo (100);

                    int cases = (int) MonteCarloAcumuladoISim;
                    
                    writer.printf("%d,%.2f,%.6f,%d%n", 
                        period, pertPct * 100, perturbedPC[period], cases);
                    
                    System.out.printf("  Pert: %+.2f%% -> Cases: %d%n", pertPct * 100, cases);
                }
            }
            
            System.out.println("Sensitivity analysis complete. Results saved\n");
            
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //-----------------------------------------------------------------------

    public static void main (String[] args) {

        boolean flagSensivel = false;

        int nLoopsMonteCarlo = 10;

        NPROBPROPAGACAO  = 8;

        if (args.length == 1) {

           nLoopsMonteCarlo = Integer.parseInt (args[0]);

           try {startBridge ();} catch(Exception e) {e.printStackTrace();};

        } else if ((args.length > 1) && Character.isDigit(args[0].charAt(0))) {

           GFlagCurvas = true;

           nLoopsMonteCarlo = Integer.parseInt (args[0]);

           for (int k = 0; k < NPROBPROPAGACAO; ++k) 
              GProbPropagacao[k] = Float.parseFloat (args[k+1]);

           try {
               GCurvasCSV = new FileWriter("curvas.csv");
           } catch (Exception e) {
               e.printStackTrace();
           }

        } else if ((args.length > 1) && !Character.isDigit((args[0].charAt(0)))) {

           for (int k = 0; k < NPROBPROPAGACAO; ++k) 
              GProbPropagacao[k] = Float.parseFloat (args[k+1]);

           flagSensivel = true;

        }

        Locale.setDefault(Locale.US);


        double menorErro = Double.MAX_VALUE;
        double[] melhoresProbabilidades = new double[3];

        System.out.println("Início.");
        System.out.println(" criando população;");

        criarPopulacao();

        System.out.println(" criando grafo;");

        wattsStrogatz(QUANTAS_PESSOAS, QUANTOS_VIZINHOS, 1, GRAPH_IRREGULAR);

        //exportarSubGrafoCSV("ografo.csv", 100);
        //if (nLoopsMonteCarlo < 10000) return;

// to activate this, ensure that only 1 monte carlo simulation loop is executed!
//        try {
//          GwriterTree = new FileWriter("tree.csv");
//        } catch (Exception e) {
//            e.printStackTrace();
//        }


        System.out.println("Simulação.");


        if (flagSensivel) {
           performSensitivityAnalysis ();
           return;
        }

        // Laço de otimização
        while (true) {

            //flag = flag - 1;

            double [] auxiliar = {0, 0, 0, 0, 0, 0};

            if (args.length == 1)
            try {

               auxiliar = pegaParametrosRede ();

               for (int k = 0; k < NPROBPROPAGACAO; ++k)
                   GProbPropagacao[k] = auxiliar[k];

            } catch(Exception e) {e.printStackTrace();};


            Grandom.setSeed(484459);

            double erro = MonteCarlo (nLoopsMonteCarlo);

            System.out.println (" RMSE: " + erro + "\n");

            if (args.length == 1)
            try{respondeRede (erro);}  catch(Exception e) {e.printStackTrace();};

            if (args.length != 1) {

                System.out.println (  "Num cases = " + MonteCarloAcumuladoISim +
                                    "\nError     = " + erro + "\n");

                break;
            }
        };


       // Feche Arquivos.
       // Sem as chamadas abaixo, Java trunca saída que restou em buffers (!).

       try {
            GCurvasCSV.close();
       } catch (Exception e) {
            e.printStackTrace();
       }


       try {
           GCurvasCSV = new FileWriter("atingidos.csv");

           for (int k = 0; k < QUANTAS_PESSOAS; k++) 
              if (0 < Gpopulacao[k].atingidos)
                 GCurvasCSV.write (  ""  + Gpopulacao[k].atingidos 
                                   + "," + Gpopulacao[k].serialinterval + "\n");

           GCurvasCSV.close();

       } catch (Exception e) {
            e.printStackTrace();
       }

        try {
            GCurvasCSV = new FileWriter("trajectory.csv");

			   GCurvasCSV.write("day,sim,data\n"); // cabeçalho

            GCurvasCSV.write (0 + "," + OsResultados[0] + "," + DadosReais[0] + "\n");

            for (int dia = 1; dia < QUANTOS_DIAS; dia++) {
                DadosReais[dia]   = DadosReais[dia]   + DadosReais[dia-1];
                GCurvasCSV.write (dia + "," + OsResultados[dia] + "," + DadosReais[dia] + "\n");
            }

            GCurvasCSV.close();

        } catch (Exception e) {
            e.printStackTrace();
        }


//       try {
//            GwriterTree.close();
//       } catch (Exception e) {
//            e.printStackTrace();
//       }



   }
}

